Better than IO, part 4, Effects in Free

Intro

So far we saw the power of monad transformers, tagless final and free monad. What else can we desire? We saw that monad transformers are quite cumbersome to use, however they are still quite helpful in a lot of scenarios.

ReaderT[EitherT[IO, Error, ?], Config, User]

Is still a a decent stack to use while accessing remote server for example. How do we use it with Free?

Can we have EitherT, StateT, OptionT along with ours NetT, LogT and DbT in a single stack using Free?
Lets give it a try.

Option in Free

Can we describe option algebra in Free? I will use Maybe as alias for Option to avoid name clashes.

sealed trait MaybeAction[A]

object MaybeAction {
  case class Just[A](value:A) extends MaybeAction[A]
  case class Nothing[A]() extends MaybeAction[A]
}

object Maybe {
  def just[A](value:A):Free[MaybeAction, A] = Free.liftF(MaybeAction.Just[A](value))
  def nothing[A]:Free[MaybeAction, A]= Free.liftF(MaybeAction.Nothing[A]())
}

Now we need a transformation from MaybeAction into IO[?]. However we have no way to represent absence of value in IO. We need some stack where we can express absence of value. Which brings us to OptionT[IO, ?]. Now we can write our transformation

class MaybeIoNat extends ~>[MaybeAction, OptionT[IO, ?]] {
  override def apply[A](fa: MaybeAction[A]): OptionT[IO, A] = fa match {
    case MaybeAction.Just(value) => OptionT.some[IO](value)
    case MaybeAction.Nothing() => OptionT.none[IO, A]
  }
}

And we can write programs like this -

def foo2(x:Int):Free[MaybeAndAppStack, User] = for {
  user <- Db.queryUser(x.toString).inject[MaybeAndAppStack]
  _ <- Log.logMsg(s"got user ${user}").inject[MaybeAndAppStack]
  _ <- Maybe.nothing[Unit].inject[MaybeAndAppStack]
  _ <- Db.storeUser(user.copy(age=user.age + 1)).inject[MaybeAndAppStack]
} yield user

This will return None. Not very useful though. Let's make our queryUser more realistic - user can be absent. And maybeQueryUser can return None.

sealed trait DbAction[A]
object DbAction {
  case class QueryUser(userId:String) extends DbAction[User]
  case class MaybeQueryUser(userId:String) extends DbAction[Option[User]]
  case class StoreUser(newUser:User) extends DbAction[Unit]
}

object Db {
  def queryUser(userId:String) = Free.liftF(DbAction.QueryUser(userId))
  def maybeQueryUser(userId:String) = Free.liftF(DbAction.MaybeQueryUser(userId))
  def storeUser(newUser:User) = Free.liftF(DbAction.StoreUser(newUser))
}

We need some way to involve Maybe with DbAction[Option[User]]. Lets try some kind of a lift. And lets start from api part - from usage.

def foo3(x:Int):Free[MaybeAndAppStack, User] = for {
  user <- Maybe.lift(Db.maybeQueryUser(x.toString).inject[MaybeAndAppStack])
  _ <- Log.logMsg(s"got user ${user}").inject[MaybeAndAppStack]
  _ <- Db.storeUser(user.copy(age=user.age + 1)).inject[MaybeAndAppStack]
} yield user

Lets try to define Maybe.lift.

object MaybeAction {
  case class Lift[A](v:Option[A]) extends MaybeAction[A]
}

object Maybe {
  def lift[F[_], A](value:Free[F, Option[A]]):Free[???, A] = value.flatMap {
    case None => nothing[A]
    case Some(v) => just(v)
  }
}

We don't know what that Maybe.lift should return. It accepts some F. But we want somehow to embed MaybeAction into that F. In the rest of our Free program we would normally use injectK for that. We need implicit injector for that.

def lift[F[_], A](value:Free[F, Option[A]])(
 implicit maybeInjector:InjectK[MaybeAction, F]):Free[F, A] = value.flatMap {
  case None => Free.liftF(maybeInjector.inj(MaybeAction.Nothing[A]()))
  case Some(v) => Free.liftF(maybeInjector.inj(MaybeAction.Just[A](v)))
}

So this basically says give me an algebra that supports maybe (through injector) and I will lift MaybeAction from option. And when we use it like this

user <- Maybe.lift(Db.maybeQueryUser(x.toString).inject[MaybeAndAppStack])

We are first injecting result of DbAction into our Maybe supporting MaybeAndAppStack. And we have injector for MaybeAction into MaybeAndAppStack.

Next

In a next part we are going to continue exploring free effects diving into Eff.

Source code

You can find source code here